package sk.datalan.core;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Iterator;

import org.openrdf.model.Graph;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.GraphImpl;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.config.RepositoryConfig;
import org.openrdf.repository.config.RepositoryConfigException;
import org.openrdf.repository.manager.RemoteRepositoryManager;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFHandler;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.RDFParseException;
import org.openrdf.rio.RDFParser;
import org.openrdf.rio.Rio;
import org.openrdf.rio.UnsupportedRDFormatException;

public final class DatabaseManager {
	private static DatabaseManager dbManager = new DatabaseManager();
	private Repository repository = null;
	private RemoteRepositoryManager repositoryManager;

	/**
	 * Setting neccessary dat
	 */
	private DatabaseManager() {

		System.setProperty( "entityExpansionLimit", "30000000" );

		Utils.log( "===== Initialize and load imported ontologies =========" );

		// The configuration file
		File configFile = new File( "C:\\Users\\Marek\\AppData\\Roaming\\Aduna\\OpenRDF Sesame console\\templates\\sestate.ttl" );
		Utils.log( "Using configuration file: " + configFile.getAbsolutePath() );

		// Parse the configuration file, assuming it is in Turtle format
		final Graph graph = parseFile( configFile, RDFFormat.TURTLE, "" );

		// Look for the subject of the first matching statement for
		// "?s type Repository"
		Iterator<Statement> iter = graph.match( null, RDF.TYPE, new URIImpl( "http://www.openrdf.org/config/repository#Repository" ) );
		Resource repositoryNode = null;
		if ( iter.hasNext() ) {
			Statement st = iter.next();
			repositoryNode = st.getSubject();
		}

		// Use this class to access a remote repository advertised by an
		// instance of the Sesame HTTP Server.
		this.repositoryManager = new RemoteRepositoryManager( "http://localhost:8080/openrdf-sesame" );

		// Initialise the repository manager
		try {
			this.repositoryManager.initialize();
			RepositoryConfig repositoryConfig = RepositoryConfig.create( graph, repositoryNode );
			this.repositoryManager.addRepositoryConfig( repositoryConfig );

		} catch ( RepositoryException e ) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch ( RepositoryConfigException e ) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public static DatabaseManager getInstance() {
		if ( dbManager == null ) {

		}
		return dbManager;
	}

	public Repository getRepository() {
		if ( this.repository == null ) {
			try {

				this.repositoryManager.refresh();
				this.repository = this.repositoryManager.getRepository( "sestate" );

				this.repository.initialize();
			} catch ( RepositoryException e ) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch ( RepositoryConfigException e ) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return this.repository;
	}

	public void resetRepository() {
		this.repository = null;
	}

	/**
	 * Parse the given RDF file and return the contents as a Graph
	 * 
	 * @param configurationFile
	 *            The file containing the RDF data
	 * @return The contents of the file as an RDF graph
	 */
	private Graph parseFile( File configurationFile, RDFFormat format, String defaultNamespace ) {
		final Graph graph = new GraphImpl();
		RDFParser parser = Rio.createParser( format );
		RDFHandler handler = new RDFHandler() {
			public void endRDF() throws RDFHandlerException {
			}

			public void handleComment( String arg0 ) throws RDFHandlerException {
			}

			public void handleNamespace( String arg0, String arg1 ) throws RDFHandlerException {
			}

			public void handleStatement( Statement statement ) throws RDFHandlerException {
				graph.add( statement );
			}

			public void startRDF() throws RDFHandlerException {
			}
		};
		parser.setRDFHandler( handler );
		try {
			parser.parse( new FileReader( configurationFile ), defaultNamespace );
		} catch ( RDFParseException e ) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch ( RDFHandlerException e ) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch ( FileNotFoundException e ) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch ( IOException e ) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return graph;
	}

	/**
	 * Load an RDF file by trying to parse in all known formats.
	 * 
	 * @param file
	 *            The file to load in to the repository.
	 */
	public void loadFile( File file ) throws RepositoryException, IOException {

		boolean loaded = false;
		RepositoryConnection con = getRepository().getConnection();
		// Try all formats
		Reader reader = null;
		try {
			long startTime = System.currentTimeMillis();
			Utils.log( "Initialize loading file '" + file.getName() + "'" );

			reader = new BufferedReader( new FileReader( file ), 1024 * 1024 * 100 );

			con.add( reader, "", RDFFormat.RDFXML );
			con.commit();

			Utils.log( "Loaded file '" + file.getName() + "' in " + ( System.currentTimeMillis() - startTime ) + "ms" );
			loaded = true;

		} catch ( UnsupportedRDFormatException e ) {
			// Format not supported, so try the next format in the list.
		} catch ( RDFParseException e ) {
			// Can't parse the file, so it is probably in another format.
			// Try the next format.
		} finally {
			if ( reader != null )
				reader.close();
		}
		if ( !loaded ) {
			con.rollback();
			Utils.log( "Failed to load '" + file.getName() + "'." );
		}
		con.close();

	}

	/**
	 * Shows the use of the special OWLIM system predicate that causes all
	 * changes to be flushed to sdisk.
	 * 
	 * @throws RepositoryException
	 */
	public void flush() throws RepositoryException {
		RepositoryConnection con = getRepository().getConnection();
		ValueFactory factory = con.getValueFactory();

		URI subj = factory.createURI( "http://www.example.com/s" );
		URI pred = factory.createURI( "http://www.ontotext.com/flush" );
		URI obj = factory.createURI( "http://www.example.com/o" );
		con.add( subj, pred, obj );
		con.commit();
		con.close();
	}

	/**
	 * Shutdown the repository and flush unwritten data.
	 */
	public void shutdown() {
		Utils.log( "===== Shutting down ==========" );
		if ( this.repository != null ) {
			try {
				this.repository.shutDown();
				this.repositoryManager.shutDown();
			} catch ( Exception e ) {
				Utils.log( "An exception occurred during shutdown: " + e.getMessage() );
			}
		}
	}
}
